package com.lagou.niancheng.rpc.client;


import com.lagou.niancheng.rpc.client.netty.NettyConnectionConfig;
import com.lagou.niancheng.rpc.client.netty.NettyConnectionSource;
import com.lagou.niancheng.rpc.common.converter.Converter;
import com.lagou.niancheng.rpc.common.converter.FastJsonConverter;
import com.lagou.niancheng.rpc.common.entity.RPCRequestData;
import com.lagou.niancheng.rpc.common.entity.RPCResponseData;
import com.lagou.niancheng.rpc.common.exceptions.SimpleRpcException;
import com.lagou.niancheng.rpc.common.utils.MethodIdUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * rpc接口代理工厂
 *
 * @author 大犬
 * @date 2020/4/27
 */
public class SimpleRpcMethodProxyFactory {

    private ConnectionSource connectionSource;
    private Converter converter;

    public SimpleRpcMethodProxyFactory() {
        this(new NettyConnectionSource(new NettyConnectionConfig()), new FastJsonConverter());
    }

    public SimpleRpcMethodProxyFactory(ConnectionSource connectionSource) {
        this(connectionSource, new FastJsonConverter());
    }

    public SimpleRpcMethodProxyFactory(ConnectionSource connectionSource, Converter converter) {
        this.connectionSource = connectionSource;
        this.converter = converter;

    }

    @SuppressWarnings("unchecked")
    public <T> T createRpcProxy(Class<T> serviceInterface) {
        // 创建rpc接口代理对象
        return (T) Proxy.newProxyInstance(SimpleRpcMethodProxyFactory.class.getClassLoader(),
                new Class[]{serviceInterface},
                new RpcProxy(connectionSource, serviceInterface, converter));
    }

    private static class RpcProxy implements InvocationHandler {
        private ConnectionSource connectionSource;
        private Class serviceInterface;
        private Converter converter;

        public RpcProxy(ConnectionSource connectionSource,
                        Class serviceInterface, Converter converter) {
            this.connectionSource = connectionSource;
            this.serviceInterface = serviceInterface;
            this.converter = converter;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String[] paramTypeNames = getParamTypes(method);
            String methodId = MethodIdUtil.getMethodId(serviceInterface.getName(), method.getName(), paramTypeNames);
            if (methodId == null) {
                return null;
            }
            // 获取连接
            Connection connection = connectionSource.getConnection();
            // 构建请求数据
            RPCRequestData rpcRequestData = buildRequestData(methodId, args);
            // 发送请求数据
            RPCResponseData rpcResponseData = (RPCResponseData) connection.sendRequestData(rpcRequestData);
            // 结果处理
            if (rpcResponseData.getOccurExp()) {
                // 如果有异常，则直接抛出
                throw rpcResponseData.getException();
            } else {
                // 返回结果
                return converter.bytes2Obj(rpcResponseData.getReturnObject(), method.getGenericReturnType());
            }
        }

        private String[] getParamTypes(Method method) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes == null || parameterTypes.length == 0) {
                return null;
            }
            String[] paramTypeNames = new String[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                paramTypeNames[i] = parameterTypes[i].getName();
            }
            return paramTypeNames;
        }

        private RPCRequestData buildRequestData(String methodId, Object[] args) {

            byte[][] paramBytes;
            if (args == null || args.length == 0) {
                paramBytes = null;
            } else {
                paramBytes = new byte[args.length][];
                for (int i = 0; i < args.length; i++) {
                    paramBytes[i] = converter.obj2Bytes(args[i]);
                }
            }
            return new RPCRequestData()
                    .setMethodId(methodId)
                    .setParams(paramBytes);
        }


    }


}
